package com.jiang.mall.mallproduct.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jiang.common.utils.PageUtils;
import com.jiang.common.utils.Query;
import com.jiang.mall.mallproduct.product.dao.CategoryDao;
import com.jiang.mall.mallproduct.product.entity.CategoryEntity;
import com.jiang.mall.mallproduct.product.service.CategoryBrandRelationService;
import com.jiang.mall.mallproduct.product.service.CategoryService;
import com.jiang.mall.mallproduct.product.vo.Catelog2Vo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    @Autowired
    private CategoryDao categoryDao;
    @Autowired
    private CategoryBrandRelationService categoryBrandRelationService;
    @Autowired
    StringRedisTemplate template;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    @Override
    public List getData() {
        return categoryDao.selectList(null);
    }

    @Override
    public void removeMenusByIds(List catIds) {
        categoryDao.deleteBatchIds(catIds);
    }

    @Override
    public Long[] getCatelogPath(Long attrGroupId) {
        List<Long> list = new ArrayList();
        Long[] longs = this.diguiCatelogPath(attrGroupId, list);
        return longs;
    }

    /**
     * spring-cache 加上 sync = true只在get方法（读）加锁，能解决缓存击穿
     * 没有在put方法（写）加锁，不能解决数据一致性
     *        @Override
     *    @SuppressWarnings("unchecked")
     *    public synchronized <T> T get(Object key, Callable<T> valueLoader) {
     *
     * 		ValueWrapper result = get(key);
     *
     * 		if (result != null) {
     * 			return (T) result.get();
     *        }
     *
     * 		T value = valueFromLoader(key, valueLoader);
     * 		put(key, value);
     * 		return value;
     *    }
     * @param categoryEntity
     */
    // cacheing 组合多个缓存操作
    // evict 删除缓存
    @Caching(
            evict = {
                    @CacheEvict(value = {"category"}, key = "'getLevel1Categories'"),
                    @CacheEvict(value = {"category"}, key = "'getCatalogJson'")
            }
    )
//    @CacheEvict(value = "category", allEntries = true) // 删除该分区下的所有数据 数据一致性-失效模式
//    @CachePut() 数据一致性-双写模式
    @Override
    public void updateCascade(CategoryEntity categoryEntity) {
        this.updateById(categoryEntity);
        categoryBrandRelationService.updateCategory(categoryEntity.getCatId(), categoryEntity.getName());

    }

    // cacheable代表当前方法的结果如果缓存中有，方法不用调用；如果缓存中没有，会调用方法
    // 默认会存储的数据采用jdk的序列化机制（自定义配置类，采用json转换数据格式），ttl时间-1（存活时间在.properties文件中设置）
    @Cacheable(value = {"category"}, key = "#root.method.name", sync = true)// 拼接上方法名 spel表达式（spring special language）
    @Override
    public List<CategoryEntity> getLevel1Categories() {
        List<CategoryEntity> categoryEntities = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));

        return categoryEntities;
    }

    @Cacheable(value = {"category"}, key = "#root.methodName")
    public Map<String, List<Catelog2Vo>> getCatalogJson() {
        Map<String, Object> map = new HashMap<>();
        List<CategoryEntity> level1Categories = getLevel1Categories();
        List<CategoryEntity> selectList = baseMapper.selectList(null);
        Map<String, List<Catelog2Vo>> map1 = level1Categories.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
            //查询这个一级分类对应的所有二级分类
            List<CategoryEntity> level2Categories = getParent_cid(selectList, v.getCatId());
            List<Catelog2Vo> catelog2Vos = null;
            if (level2Categories != null) {
                catelog2Vos = level2Categories.stream().map(l2 -> {
                    Catelog2Vo catelog2Vo = new Catelog2Vo();
                    catelog2Vo.setCatelog1Id(v.getCatId().toString());
                    catelog2Vo.setId(l2.getCatId());
                    catelog2Vo.setName(l2.getName());
                    //查询这个二级分类对应的所有三级分类
                    List<CategoryEntity> level3Categories = getParent_cid(selectList, l2.getCatId());
                    if (level3Categories != null) {
                        List<Catelog2Vo.Catalog3Vo> catalog3Vos = level3Categories.stream().map(l3 -> {
                            Catelog2Vo.Catalog3Vo catalog3Vo = new Catelog2Vo.Catalog3Vo();
                            catalog3Vo.setCatelog2Id(l2.getCatId().toString());
                            catalog3Vo.setId(l3.getCatId());
                            catalog3Vo.setName(l3.getName());
                            return catalog3Vo;
                        }).collect(Collectors.toList());
                        catelog2Vo.setCatalog3List(catalog3Vos);
                    }

                    return catelog2Vo;
                }).collect(Collectors.toList());
            }

            return catelog2Vos;
        }));

        return map1;
    }

    public Map<String, List<Catelog2Vo>> getCatalogJson2() {
        String catalogJson = template.opsForValue().get("catalogJson");
        if (StringUtils.isEmpty(catalogJson)) {
            System.out.println("....缓存没命中");
            Map<String, List<Catelog2Vo>> catalogJsonFromDB = getCatalogJsonFromRedis();

            return catalogJsonFromDB;
        }
        System.out.println("....缓存命中");
        Map<String, List<Catelog2Vo>> stringListMap = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catelog2Vo>>>() {
        });
        return stringListMap;
    }

    public Map<String, List<Catelog2Vo>> getCatalogJsonFromRedis() {
        // 去redis占坑
        String uuid = UUID.randomUUID().toString();
        // 加锁 与 设置过期时间 要同步执行。
        // 为什么要保证加锁与设置过期时间的原子性呢？因为锁加上的那一刻服务宕机，没有给锁赋上过期时间。导致锁一直被占用
        // 为什么要设置锁的过期时间？发生异常，锁没有删除。设置过期时间锁自动删除。
        Boolean lock = template.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);
        if (lock) {

            System.out.println("获取锁");
            Map<String, List<Catelog2Vo>> dbBasic = null;
            // 为什么要用 try 包裹呢？因为 getDbBasic() 的业务耗时万一有异常，锁一直被占用。所以无论如何最终一定要执行删除锁的方法
            try {
                dbBasic = getDbBasic();
            } finally {
                // 为什么要用到 uuid 呢？因为业务代码（查询数据库；redis获得锁值的交互）执行时间过长的话，
                // 在执行删锁之前：锁过期，其它线程抢占锁。那么这时删的锁就是别的线程的锁。使用uuid可以确保删除的是自己线程的锁。
//                String lockValue = template.opsForValue().get("lock");
//                if(uuid.equals(lockValue)) {
//                    template.delete("lock");
//                }
                // 获取锁值对比 与 删除锁 要同步。与加锁并赋上过期时间的原子性同理
                String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
                // 删除锁(lock=uuid)
                Long lock1 = template.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid);
                System.out.println("删除锁");
            }
            return dbBasic;
        } else {
            System.out.println("获取分布式锁失败....等待重试");
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 自旋锁
            return getCatalogJsonFromRedis();
        }
    }

    /**
     * 公共方法，查询数据库
     * @return
     */
    public Map<String, List<Catelog2Vo>> getDbBasic() {
        // 在加锁的情况下，让那些为 第一次查询是否有缓存的线程 所排队等待中的其余所有线程进行一个判断，已有缓存，不必进入
        String catalogJson = template.opsForValue().get("catalogJson");
        if (!StringUtils.isEmpty(catalogJson)) {
            System.out.println("....缓存命中");
            Map<String, List<Catelog2Vo>> stringListMap = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catelog2Vo>>>() {
            });
            return stringListMap;
        }
        System.out.println("....查询了数据库");
        Map<String, Object> map = new HashMap<>();
        List<CategoryEntity> level1Categories = getLevel1Categories();
        List<CategoryEntity> selectList = baseMapper.selectList(null);
        Map<String, List<Catelog2Vo>> map1 = level1Categories.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
            //查询这个一级分类对应的所有二级分类
            List<CategoryEntity> level2Categories = getParent_cid(selectList, v.getCatId());
            List<Catelog2Vo> catelog2Vos = null;
            if (level2Categories != null) {
                catelog2Vos = level2Categories.stream().map(l2 -> {
                    Catelog2Vo catelog2Vo = new Catelog2Vo();
                    catelog2Vo.setCatelog1Id(v.getCatId().toString());
                    catelog2Vo.setId(l2.getCatId());
                    catelog2Vo.setName(l2.getName());
                    //查询这个二级分类对应的所有三级分类
                    List<CategoryEntity> level3Categories = getParent_cid(selectList, l2.getCatId());
                    if (level3Categories != null) {
                        List<Catelog2Vo.Catalog3Vo> catalog3Vos = level3Categories.stream().map(l3 -> {
                            Catelog2Vo.Catalog3Vo catalog3Vo = new Catelog2Vo.Catalog3Vo();
                            catalog3Vo.setCatelog2Id(l2.getCatId().toString());
                            catalog3Vo.setId(l3.getCatId());
                            catalog3Vo.setName(l3.getName());
                            return catalog3Vo;
                        }).collect(Collectors.toList());
                        catelog2Vo.setCatalog3List(catalog3Vos);
                    }

                    return catelog2Vo;
                }).collect(Collectors.toList());
            }

            return catelog2Vos;
        }));

        // 将对象转换成Json储存在缓存中
        String s = JSON.toJSONString(map1);
        template.opsForValue().set("catalogJson", s);
        return map1;
    }

    public Map<String, List<Catelog2Vo>> getCatalogJsonWithLocalLock() {
        // 锁的时序问题
        // 从（1）判断缓存不存在、（2）查询数据库和（3）落入缓存都要放在锁中。这三步为一个原子操作
        synchronized (this) {
            // 在加锁的情况下，让那些为第一次线程所排队等待的线程进行一个判断，已有缓存，不必进入
            String catalogJson = template.opsForValue().get("catalogJson");
            if (!StringUtils.isEmpty(catalogJson)) {
                System.out.println("....缓存命中");
                Map<String, List<Catelog2Vo>> stringListMap = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catelog2Vo>>>() {
                });
                return stringListMap;
            }

            return getDbBasic();
        }
    }

    private List<CategoryEntity> getParent_cid(List<CategoryEntity> selectList, Long parent_cid) {
        List<CategoryEntity> collect = selectList.stream().filter(i -> i.getParentCid().equals(parent_cid)).collect(Collectors.toList());
        return collect;
    }

    // 递归查找 catelogPath
    public Long[] diguiCatelogPath(Long attrGroupId, List<Long> list) {
        CategoryEntity categoryEntity = this.getById(attrGroupId);
        list.add(categoryEntity.getCatId());
        if (categoryEntity.getParentCid() != 0) {
            this.diguiCatelogPath(categoryEntity.getParentCid(), list);
        }
        Collections.reverse(list);
        return (Long[]) list.toArray(new Long[list.size()]);
    }
}